数据大屏实现揭秘——以火山引擎数据大屏产品为例
发表时间:Wed Nov 29 2023 18:15:15 GMT+0800 (中国标准时间)
点击上方关注
技术干货不迷路
引言
在大屏产品中,可视化扮演着信息展示和传达、用户体验和互动、数据分析和决策支持、品牌展示和差异化、故事叙述和信息呈现等至关重要的角色。作为可视化图表的重要载体之一,大屏与智能 BI 产品不管是在产品设计,还是可视化设计的侧重点都有很大不同。
智能BI产品
数据大屏产品
本文以 DataWind 产品数据大屏为例,为您揭示如何建设令人叹为观止的数据大屏。
场景化的主题色彩配置
为不同行业的数据大屏使用不同的颜色主题可以提高数据可视化效果、增强数据传达的意义、提高品牌识别度和满足用户需求,从而更好地呈现数据。
图表库能够支持场景化的主题色彩配置,这意味着用户可以根据不同的行业需求来选择不同的主题色彩,以更好地呈现数据。在不同的行业中,用户对于数据可视化的需求和期望可能会有所不同,因此场景化的主题色彩配置可以帮助用户更好地满足其特定的需求。
例如,在金融行业中,用户可能更注重数据的准确性和可靠性,因此金融行业的图表库可能需要提供更加严肃和专业的主题色彩配置;而在广告行业中,用户更注重图表的视觉效果和吸引力,因此广告行业的图表库可能需要提供更加鲜艳和夸张的主题色彩配置。
不同场景下的案例效果
分析场景
金融场景
文旅场景
实现揭秘
从上述案例中,我们可以注意到大屏可视化色彩设计有两个明显的特点:1、行业相关联的颜色主题;2、图元渐变着色。
颜色主题注册和切换
主题色板的构造基于于语义化及美观设计原则,即结合使用场景(保证大屏主题的场景表现力)、配色公式(保证图元在美观度、差异度等方面的配色效果和信息表达力)等逻辑进行设计。而针对大屏业务场景,我们沉淀出一套色彩方案,涵盖党建、金融、科技、文旅等行业,结合 VChart 主题注册和切换能力,做到开箱即用。
色板的具体信息开放出来供大家参考: _https://github.com/VisActor/VChart/blob/develop/docs/assets/themes/colors.json_
核心代码
const response = await fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/theme.json'); const colorTheme = await response.json(); // 注册主题 const theme = {}; for (const colorKey in colorTheme) { const colorName = colorTheme[colorKey].name; theme[colorName] = { colorScheme: { default: colorTheme[colorKey].colors } }; VChart.ThemeManager.registerTheme(colorKey, theme[colorName]); } // 主题切换 VChart.ThemeManager.setCurrentTheme('volcanoBlue');
在线示例:_https://www.visactor.io/vchart/guide/tutorial\_docs/Theme/Customize\_Theme_
渐变效果实现
纯色到渐变色的转换:纯色 => 图元填充渐变 + 图元描边边渐变。
示例地址: _https://codesandbox.io/s/bar-gradient-ycr8m8_
核心代码
const gradientCallback = (datum, ctx, type) => { return { gradient: "linear", x0: 0, y0: 0, x1: 0, y1: 1, stops: [ { offset: 0, fillOpacity: 0, color: hexToRgba(ctx.seriesColor(datum.type), 1), }, { offset: 1, fillOpacity: 1, color: hexToRgba(ctx.seriesColor(datum.type), 0), } ] }; }; // 以同样的方式在主题中注册和切换 const theme = { series: { bar: { bar: { style: { fill: (datum, ctx) => gradientCallback(datum, ctx, "fill"), stroke: (datum, ctx) => gradientCallback(datum, ctx, "stroke"), lineWidth: 2 } } } } } VChart.ThemeManager.registerTheme(theme, 'gradient'); VChart.ThemeManager.setCurrentTheme('gradient');
渐变色详细说明参考:_https://www.visactor.io/vchart/guide/tutorial\_docs/Chart\_Concepts/Series/Mark_
最终效果
最终呈现的图表视觉效果不管是在提高吸引力,引流观众方面,还是在提升场景辨识度上都颇具成效。
在线 Demo
示例地址:_https://www.visactor.io/vchart/demo/theme/theme-style_
更多主题教程见: _**https://www.visactor.io/vchart/guide/tutorial\_docs/Theme/Theme**_
高度定制化的辅助装饰
在图表图元上做细微的装饰不仅可以提高视觉吸引力,增加图表的美感和吸引力,使得读者更愿意阅读和理解数据;更重要地,它还可以增加品牌识别度,帮助提高品牌识别度和专业感,使得数据大屏更具个性化和品牌化。
在图表中,图元负责实现数据到图形的映射,比如:数值的大小映射为矩形的高度,数值的类型映射为矩形的颜色等。而组件则负责数据的数值标记、图元的交互,比如:坐标轴以标签和刻度的形式标记某个高度对应的具体数值大小。
辅助装饰通常围绕着图元和组件展开,对图元的辅助装饰负责突出数据,对组件的装饰则负责个性化展示。
实现揭秘
图元装饰
由于装饰的位置强依赖于图元,在大屏侧无法准确定位并添加。通过 VChart 提供拓展 Mark[1]配置能力可以直接渲染出依附于既有图元的装饰图元,完美解决这一问题。
示例地址: _https://codesandbox.io/s/line-with-halo-j54hv8_
示例地址: _https://codesandbox.io/s/line-with-halo-forked-xccmvq?file=/src/index.ts_
核心代码
extensionMark: [ { name: "markSymbol", type: "symbol", dataId: "data", // 绑定的数据id visible: true, style: { x: (datum, ctx, elements, dataView) => { return ctx.valueToX([datum["beinirRbfVnf"]]); // 自定义x映射 }, y: (datum, ctx, elements, dataView) => { return ctx.valueToY([datum["10002"]]); // 自定义y映射 }, size: 13, fillOpacity: 0.1, fill: "#FFF", strokeOpacity: 0.5, lineWidth: 1, stroke: { gradient: "conical", startAngle: 0, endAngle: 6.283185307179586, stops: [ { offset: 0, color: "rgba(255, 255, 255, 0)" }, { offset: 1, color: "rgba(255, 255, 255, 1)" } ] } } } ]
通过 VChart 自定义渲染能力,还可以支持更多图元的纹理装饰。
组件装饰
为了标记出坐标轴的覆盖范围,我们需要增加轴辅助装饰。用 VChart 的轴 tick 回调函数可以实现这一效果。实现原理是,根据回调中的 index 判断 tick 是否是第一个或最后一个,如果是的话则设置为可见,不是的话则隐藏。
示例地址: _https://codesandbox.io/s/axes-with-tick-sign-9n9jtf_
核心代码
axes: [{ // ... tick: { size: 6, visible: true, style: (...args: any[]) => { // args[1]为tick index, args[3]为tick全量数据 tickCount = args[3].length - 1; return { lineWidth: args[1] === 0 || args[1] === tickCount ? 2 : 0, stroke: "rgb(0,110,255)" }; } } }]
丰富灵活的动画效果
从数据场景而言,大屏通常用于展示实时数据、动态信息和即时反馈。通过动态效果,可以更好地呈现和展示这些数据的动态变化和趋势,使观众能够及时了解最新的数据情况。
从现实场景而言,大屏通常在公共场所或会议展览等场合使用,需要通过瞬间的视觉冲击来吸引人们的注意,使他们停下来观看。
不同图表的动画效果
设计动画的前提是明确动画的目标,不同的目标可能需要不同类型、频率和复杂程度的动画实现。显而易见,贯穿大屏数据可视化场景的动画目标如下:
强调数据重点和变换,通过设计合适的过渡和动作,可以使关键数据或信息在动画中突出显示。重点和关键变化的动画应该被放在视觉的焦点位置,使其更易于观察和理解。
吸引观众的注意,通过炫酷的动态效果可以迅速抓人眼球,但同时又需要控制速度和流畅度,以免影响观感。
根据目标在大屏中可以总结出数据更新动画、高亮动画和氛围动画,不同图元的动画效果各不相同。
柱图数据更新动画
示例地址: _https://codesandbox.io/s/animation-bar-yypwgs?file=/src/index.ts_
柱图氛围动画
示例地址: _https://www.visactor.io/vchart/guide/tutorial\_docs/extend/custom\_animation_
面积图数据更新动画
示例地址: _https://codesandbox.io/s/animation-line-6nlpd4?file=/src/index.ts_
除上述图表外,还有饼图、散点图等基本图表类型对应的动画,在此不一一赘述。
实现揭秘
VChart 动画的实现依赖于 VRender 绘图引擎与 VGrammar 可视化语法。从实现分工而言,VRender 提供任意图形的形变能力,VGrammar 负责控制形变动画的流程,VChart 进行上层封装并将配置以简洁易用的方式暴露给用户。
对于上述动画,VChart 层的实现主要依赖于对 VGrammar 动画语法的封装。
柱图数据更新动画
示例地址:_https://codesandbox.io/s/bar-update-animation-7jkd3j?file=/src/index.ts_
核心代码
animationUpdate: { type: 'moveIn', duration: 500 }
柱图数据高亮动画
示例地址: _https://codesandbox.io/s/animation-highlight-j6d4f2?file=/src/index.ts_
核心代码
animationNormal: { bar: [ { loop: true, startTime: 100, oneByOne: 100, timeSlices: [ { delay: 1000, effects: { channel: { fillOpacity: { to: 0.5 } }, easing: "linear" }, duration: 500 }, { effects: { channel: { fillOpacity: { to: 1 } }, easing: "linear" }, duration: 500 } ] } ] }
柱图氛围动画
示例地址: _https://www.visactor.io/vchart/guide/tutorial\_docs/extend/custom\_animation_
核心代码
animationNormal: { bar: { loop: 100, duration: 1500, easing: 'quadIn', custom: VRender.StreamLight, customParameters: { attribute: { fillColor: '#bcdeff', opacity: 0.3, blur: 20, shadowColor: '#bcdeff', width: 160 } } } }
图表功能的可拓展性
除了提供多种预定义的图表类型,如柱状图、折线图、饼图等,我们还支持用户根据自己的数据特点和展示需求,创建和自定义各种类型的图表。使用 VGranmar 图形语法,你可以完成数据到图形的自定义映射,画布的自定义布局,动画效果和流程的自定义编排以及交互功能的自定义配置。
比如在大屏新增的排行榜组件并非 VChart 既有图表类型,而是通过图形语法 VGrammar 完全自定义实现。
实现揭秘
自定义映射
首先,要区分构成排行榜需要的图元类型,它们分别是矩形、标题、标签、装饰点。其次,需要确定图元的属性与数据的对应关系。
以如下数据为例:
const data = [ { category: '吉林', value: 50 }, { category: '内蒙古', value: 40 }, { category: '河北', value: 30 }, { category: '湖南', value: 30 }, { category: '江西', value: 24 }, ]
图元及数据与数据的映射关系:
映射结果
在线示例: _https://codesandbox.io/s/vgrammar-ranking-list-animation-dr87sy_
核心代码
marks: [ // 矩形 { type: 'rect', from: { data: chartSpec.data[0].id }, dependency: ['viewBox', 'xScale', 'yScale'], encode: { update: (datum, element, params) => { return { x: params.xScale.scale(dataMin), // 根据xScale做数据映射计算 y: params.yScale.scale(datum['category']), // 根据yScale做数据映射计算 width: params.xScale.scale(datum['value']), // 根据xScale做数据映射计算 height: barWidth, // ...省略其他属性 } }, }, }, // 装饰点 { type: 'symbol', from: { data: chartSpec.data[0].id }, dependency: ['viewBox', 'yScale', 'xScale'], encode: { update: (datum, element, params) => { return { x: params.xScale.scale(replaceNilDatum(datum, 'value', dataMin)), y: params.yScale.scale(datum['category']), // ...省略其他属性 } }, }, }, // 标题 { type: 'text', from: { data: chartSpec.data[0].id }, dependency: ['viewBox', 'yScale', 'xScale'], encode: { update: (datum, element, params) => { return { text: leftTextFormatMethod(datum['category']), x: params.xScale.scale(dataMin), y: params.yScale.scale(datum['category']), // ...省略其他属性 } }, }, }, // 标签 { type: 'text', from: { data: chartSpec.data[0].id }, dependency: ['viewBox', 'yScale'], encode: { update: (datum, element, params) => { return { text: rightTextFormatMethod(datum['value']), x: params.viewBox.x2, y: params.yScale.scale(datum['category']), // ...省略其他属性 } }, }, }, ],
自定义动画
排行榜组件的动画分为:入场动画、数据更新动画和退场动画。
入场动画时,所有元素的 y 属性从画布外,变为正常状态。
数据更新时,矩形图元的 width 属性从 0 变为正常状态。
退场动画时,所有元素的 y 属性从正常状态变为画布外。
在线示例: _https://codesandbox.io/s/vgrammar-ranking-list-animation-dr87sy_
核心代码
// 以矩形图元为例 // 入场动画 enter: [ { delay: 0, duration: durationTime, channel: { dy: { from: (datum, element, params) => { return params.viewBox.y2 }, to: 0, }, }, }, ], // 更新动画 enter: [ { delay: 0, duration: durationTime, channel: { width: { from: 0, to: (datum, element, params) => { return params.xScale.scale(datum['value']) } } } ], // 退场动画 exit: [ { delay: 0, duration: durationTime, channel: { dy: { from: 0, to: (datum, element, params) => { return params.viewBox.y2 }, }, }, }, ],
自定义交互
在 VChart 中,每个内置图表都有对应的图元点击事件用于数据联动。为了对齐这个能力,自定义图表排行榜也需要增加图元点击事件。依赖于 vgrammr 的事件注册能力,只需要在实例上调用addEventListener
即可实现。
在线示例: _https://codesandbox.io/s/vgrammar-ranking-list-animation-dr87sy_
核心代码
this.chartInstance.addEventListener('pointerdown', this.clickEventHandler)
结语
本文以 DataWind 数据大屏产品为例,详细介绍了实现一个优秀的数据大屏在技术和设计上要考虑的内容和实现方法,希望对您有所启发。
联系与体验方式
火山引擎 DataWind:_https://www.volcengine.com/product/datawind_
VisActor 项目官网:_https://www.visactor.io/_
今夜无月,期待你点亮星空,感谢 Star:
github:_**https://github.com/VisActor/VChart**_
参考资料
[1]
拓展 Mark: _https://www.visactor.io/vchart/guide/tutorial\_docs/extend/custom\_mark_
[2]
探索 VChart 图表库:简单、易用、强大、炫酷的可视化利器: _https://juejin.cn/post/7291856254098636852_
[3]
VTable——不只是高性能的多维数据分析表格,开源,免费,百万数据秒级渲染: _https://juejin.cn/post/7287214029449805836_
[4]
GPT 遇到可视化——一句话生成图表、图片和视频: _https://juejin.cn/post/7280741167272050744_
[5]
魔力之帧(上):前端图表库动画实现原理: _https://juejin.cn/post/7275270809777520651_
[6]
火山引擎 DataWind 产品可视化能力揭秘: _https://juejin.cn/post/7269757651772276790_
[7]
VisActor——面向叙事的智能可视化解决方案: _https://juejin.cn/post/7259385807550513210_
[8]
基于 VTable 的多维数据展示的原理与实践: _https://juejin.cn/post/7296782121153642531_